package com.example.demo.shiro.filter;

import com.example.demo.common.FastJsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.MediaType;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 修改后的 perms 过滤器, 添加对 restful 请求的支持. 统一返回 json
 *
 * @author zhaiding
 * @since 2020-01-08
 */
@Slf4j
public class RestAuthorizationFilter extends PermissionsAuthorizationFilter {

    /**
     * 重写URL匹配  加入 httpMethod 支持
     */
    @Override
    protected boolean pathsMatch(String path, ServletRequest request) {
        String requestURI = this.getPathWithinApplication(request);
        // path: url==method eg: http://api/menu==GET   需要解析出path中的url和httpMethod
        String[] strings = path.split("==");
        if (strings.length <= 1) {
            // 分割出来只有URL
            return this.pathsMatch(strings[0], requestURI);
        } else {
            // 分割出url+httpMethod,判断httpMethod和request请求的method是否一致,不一致直接false
            String httpMethod = WebUtils.toHttp(request).getMethod().toUpperCase();
            return httpMethod.equals(strings[1].toUpperCase()) && this.pathsMatch(strings[0], requestURI);
        }
    }

    /**
     * 登录 和 权限校验
     */
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        String token = httpServletRequest.getHeader("Authorization");
        Subject subject = getSubject(request, response);
        try {
            // 委托给Realm进行登录
            subject.login(new JwtToken(token));
        } catch (AuthenticationException e) {
            return false;
        }
        // 授权校验
        return checkPerms(subject, mappedValue);
    }

    /**
     * 当没有权限被拦截时:
     * 如果是 AJAX 请求, 则返回 JSON 数据.
     * 如果是普通请求, 则跳转到配置 UnauthorizedUrl 页面.
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        // 如果未登录
        if (subject.getPrincipal() == null) {
            if (log.isDebugEnabled()) {
                String httpMethod = WebUtils.toHttp(request).getMethod().toUpperCase();
                log.debug("用户: [{}] 请求 restful url :{} {}, token校验被拦截.", subject.getPrincipal(), httpMethod, this.getPathWithinApplication(request));
            }
            // 请求返回 JSON
            Map<String, Object> map = new HashMap<>();
            map.put("code", HttpServletResponse.SC_UNAUTHORIZED);
            map.put("msg", "token 验证失败");
            writeJson(map, response, HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        } else {
            // 如果已登陆, 但没有权限
            //请求返回 JSON
            if (log.isDebugEnabled()) {
                String httpMethod = WebUtils.toHttp(request).getMethod().toUpperCase();
                log.debug("用户: [{}] 请求 restful url :{} {}, 无权限被拦截.", subject.getPrincipal(), httpMethod, this.getPathWithinApplication(request));
            }
            String[] perms = (String[]) mappedValue;
            Map<String, Object> map = new HashMap<>();
            map.put("code", HttpServletResponse.SC_FORBIDDEN);
            map.put("msg", String.format("没有 %s 权限,请联系管理员添加", perms[0]));
            writeJson(map, response, HttpServletResponse.SC_FORBIDDEN);
        }
        return false;
    }


    /**
     * 验证当前用户是否拥有mappedValue任意一个权限
     */
    private boolean checkPerms(Subject subject, Object mappedValue) {
        String[] perms = (String[]) mappedValue;
        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
            if (perms.length == 1) {
                isPermitted = subject.isPermitted(perms[0]);
            } else {
                isPermitted = subject.isPermittedAll(perms);
            }
        }
        return isPermitted;
    }

    /**
     * 输出JSON
     */
    public static void writeJson(Object object, ServletResponse response, int status) {
        PrintWriter out = null;
        try {
            HttpServletResponse httpServletResponse = WebUtils.getHttpResponse(response);
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            httpServletResponse.setStatus(status);
            out = httpServletResponse.getWriter();
            out.write(FastJsonUtils.toJsonStr(object));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
}
